package com.riguz.springcloud.service;

import com.google.common.base.Strings;
import com.riguz.springcloud.constants.Status;
import com.riguz.springcloud.dao.UserMapper;
import com.riguz.springcloud.exception.DatabaseException;
import com.riguz.springcloud.exception.InvalidPasswordException;
import com.riguz.springcloud.exception.ResourceIsDeletedxception;
import com.riguz.springcloud.exception.ResourceNotFoundException;
import com.riguz.springcloud.id.IdGenerator;
import com.riguz.springcloud.model.User;
import com.riguz.springcloud.model.UserExample;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.List;
import java.util.Optional;

@Service
@Transactional
public class UserService {
    private static final Logger logger = LoggerFactory.getLogger(UserService.class);
    @Autowired
    UserMapper userMapper;

    @Autowired
    IdGenerator idGenerator;

    @Autowired
    PasswordEncoder passwordEncoder;

    @Transactional(readOnly = true)
    public User findUserById(String id) {
        return getUserIfExists(id);
    }

    private UserExample createBasicQueryExample() {
        UserExample args = new UserExample();
        args.createCriteria().andDeletedEqualTo(Status.DELETED.FALSE.getValue());
        return args;
    }

    @Transactional(readOnly = true)
    public List<User> findUserByName(String name) {
        UserExample args = createBasicQueryExample();
        args.getOredCriteria().get(0).andNameEqualTo(name);
        return userMapper.selectByExample(args);
    }

    @Transactional(readOnly = true)
    public List<User> findUsers(String search, Integer status) {
        UserExample args = createBasicQueryExample();
        UserExample.Criteria criteria = args.getOredCriteria().get(0);
        if (!Strings.isNullOrEmpty(search))
            criteria.andNameLike("%" + search + "%");
        if (status != null)
            criteria.andStatusEqualTo(status.byteValue());
        return userMapper.selectByExample(args);
    }

    public static final String DEFAULT_PASSWORD = "123456";

    public User createUser(User user) {
        user.setDeleted(Status.DELETED.FALSE.getValue());
        user.setUserId(idGenerator.generateId("user"));
        user.setCreateTime(new Date());
        user.setStatus(Status.USER_STATUS.UN_ACTIVATED.getValue());
        user.setErrorCount(0);
        user.setPassword(passwordEncoder.encode(DEFAULT_PASSWORD));
        int inserted = userMapper.insert(user);
        checkAffectedRows(inserted, 1);
        return user;
    }

    private void checkAffectedRows(int affected, int expected) {
        if (affected != expected)
            throw new DatabaseException("Expected to affect " + expected + " rows but actual is:" + affected);
    }

    private User unsetSensitiveFieldsNoUpdate(User user) {
        user.setDeleted(null);
        user.setStatus(null);
        user.setPassword(null);
        user.setCreateTime(null);
        user.setErrorCount(null);
        return user;
    }

    private User getUserIfExists(String userId, boolean checkDeleted) {
        User user = userMapper.selectByPrimaryKey(userId);
        if (user == null)
            throw new ResourceNotFoundException(userId);
        if (checkDeleted && user.getDeleted() == Status.DELETED.TRUE.getValue())
            throw new ResourceIsDeletedxception(userId);
        return user;
    }

    private User getUserIfExists(String userId) {
        return getUserIfExists(userId, true);
    }

    public User updateUser(String id, User user) {
        User current = getUserIfExists(id);
        user.setUserId(id);
        user = unsetSensitiveFieldsNoUpdate(user);
        int updated = userMapper.updateByPrimaryKeySelective(user);
        checkAffectedRows(updated, 1);
        return getUserIfExists(id);
    }

    public User updatePassword(String userId, String newPassword) {
        User user = getUserIfExists(userId);
        user = unsetSensitiveFieldsNoUpdate(user);
        user.setPassword(passwordEncoder.encode(newPassword));
        user.setStatus(Status.USER_STATUS.NORMAL.getValue());
        int updated = userMapper.updateByPrimaryKeySelective(user);
        checkAffectedRows(updated, 1);
        return getUserIfExists(userId);
    }

    public User deleteUser(String userId) {
        User user = getUserIfExists(userId);
        user = unsetSensitiveFieldsNoUpdate(user);
        user.setDeleted(Status.DELETED.TRUE.getValue());
        int updated = userMapper.updateByPrimaryKeySelective(user);
        checkAffectedRows(updated, 1);
        return getUserIfExists(userId, false);
    }

    @Transactional(readOnly = true)
    public User validatePassword(String name, String password) {
        List<User> matchedUsers = findUserByName(name);
        if (matchedUsers.isEmpty())
            throw new ResourceNotFoundException(name);
        if (matchedUsers.size() > 1) {
            logger.error("Duplicate user record:{}, records:{}", name, matchedUsers);
            throw new DatabaseException("Duplicate record found in database");
        }
        User matchedUser = matchedUsers.get(0);
        if (matchedUser.getStatus() == Status.USER_STATUS.UN_ACTIVATED.getValue())
            throw new InvalidPasswordException("User not activated");
        if (!passwordEncoder.matches(password, matchedUser.getPassword()))
            throw new InvalidPasswordException("Password or user name not match");
        return matchedUser;
    }
}
